package com.kili.file.utils;


import com.google.common.base.Preconditions;
import com.kili.file.enums.FileContentType;
import com.kili.file.enums.FileStatus;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Objects;

import static com.kili.file.enums.FileMagicNumType.FILE_HEX_MAP;


/**
 * FileUtil
 *
 * @author gordon
 */
@Slf4j
public class FileUtil {

    public static FileStatus validFile(MultipartFile file) {

        //setup initialed valid
        initializedValidation(file);

        // check file suffix name
        Map.Entry<String, String> fileEntry = secureFileSuffixWithHex(file);
        if (Objects.isNull(fileEntry)) {
            return validFileHex(file);
        }

        String fileType = fileEntry.getKey();
        String fileHex = fileEntry.getValue();


        // check file content type
        if (!matchFileContentType(fileType, file.getContentType())) {
            log.warn("file content type do not match - file : {} fileContentType : {}", file, file.getContentType());
            return FileStatus.ILLEGAL_TYPE;
        }

        //if the file has no magic number, return regular
        if (StringUtils.EMPTY.equals(fileHex)) {
            return FileStatus.REGULAR;
        }

        // check magic number
        return validFileHex(file, fileHex);
    }

    private static FileStatus validFileHex(MultipartFile file) {
        byte[] b = new byte[30];
        try (InputStream is = file.getInputStream()) {
            is.read(b, 0, 30);
        } catch (IOException e) {
            log.error("File IO stream read exception", e);
            return FileStatus.IRREGULAR;
        }
        String hex = getFileHexString(b);

        Map.Entry<String, String> map = FILE_HEX_MAP.
                entrySet().
                stream().
                filter(e -> hex.startsWith(e.getValue())).
                findFirst().
                orElse(null);

        return map == null ? FileStatus.IRREGULAR : FileStatus.REGULAR;
    }

    private static FileStatus validFileHex(MultipartFile file, String fileHex) {
        byte[] b = new byte[30];
        try (InputStream is = file.getInputStream()) {
            is.read(b, 0, 30);
            if (!matchFileType(b, fileHex)) {
                log.warn("File magic number verification failed , " +
                        "source file magic is {} target file magic is {}", b, fileHex);
                return FileStatus.ILLEGAL_TYPE;
            }
        } catch (IOException e) {
            log.error("File IO stream read exception", e);
            return FileStatus.IRREGULAR;
        }
        return FileStatus.REGULAR;
    }


    private static void initializedValidation(MultipartFile file) {
        Preconditions.checkState(!file.isEmpty(), "Multipart request file from Current request is null ");
        Preconditions.checkState(org.springframework.util.StringUtils.hasText(file.getOriginalFilename()),
                "Original file name is empty.");
    }


    private static Map.Entry<String, String> secureFileSuffixWithHex(MultipartFile file) {
        String suffixName = getFileSuffixName(file.getOriginalFilename());
        log.info("Current multi-File suffix name is  {}", suffixName);
        return compareSuffixName(suffixName);
    }


    public static String getFileSuffixName(String fileName) {
        return StringUtils.substring(fileName, fileName.lastIndexOf(".") + 1);
    }


    private static boolean matchFileContentType(String fileType, String fileContentType) {
        return FileContentType.
                FILE_CONTENT_TYPE_MAP.
                entrySet().
                stream().
                anyMatch(e -> fileType.equalsIgnoreCase(e.getKey()) &&
                        fileContentType.equalsIgnoreCase(e.getValue()));
    }


    private static boolean matchFileType(byte[] b, String fileHex) {
        String fileTypeHex = String.valueOf(getFileHexString(b));
        return StringUtils.isNotBlank(fileTypeHex) && fileTypeHex.toUpperCase().startsWith(fileHex);
    }


    private static Map.Entry<String, String> compareSuffixName(String suffixName) {

        return
                FILE_HEX_MAP.
                        entrySet().
                        stream().
                        filter(e -> suffixName.equalsIgnoreCase(e.getKey())).
                        findFirst().
                        orElse(null);


    }

    private static String getFileHexString(byte[] var0) {
        StringBuilder stringBuilder = new StringBuilder();
        if (var0 == null || var0.length <= 0) {
            return null;
        }
        for (byte var1 : var0) {
            int v = var1 & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                stringBuilder.append(0);
            }
            stringBuilder.append(hv);
        }
        return stringBuilder.toString();
    }
}